'use strict';

const Service = require('egg').Service;
const crypto = require('crypto');
const UUID = require('uuid').v4;
const dayjs = require('dayjs');
const fs = require('fs');
const path = require('path');


class FileService extends Service {
  async uploadFile({ category, single }) {
    const { ctx } = this;
    ctx.logger.info('===reviceFile====', ctx.request.files);
    if (!ctx.request.files || ctx.request.files.length === 0) {
      return ctx.helper.body.ERROR({ ctx, msg: '请选择文件' });
    }
    const rets = [];
    // 拿到临时文件循环操作
    const files = ctx.request.files;
    for (let i = 0; i < files.length; i++) {
      if (single && i > 0) { // 如果单文件上传只处理第一张，然后结束循环
        break;
      }
      const file = files[i];
      try {
        // 获取文件的基本信息：大小，hash，fileBuffer
        const { size, md5, data } = await this.checkFileInfo(file);
        // 数据库中查询是否存在，如果存在就直接返回文件信息，不保存文件。
        const exists = await this.checkFileExists(md5);
        if (exists) {
          rets.push(exists);
        } else { // 如果不存在就将文件保存到服务器中，并且数据库中创建记录。
          // 保存文件到硬盘
          const res = await this.putFile(data, file.filename, category);
          // 保存文件信息到数据库
          const rsp = await this.app.model.TFile.create({
            path: res.path.slice(3), // 去掉app这层路径
            name: file.filename,
            category,
            ext: res.ext,
            size,
            md5,
            create_time: new Date(),
          });
          rets.push({
            id: rsp.dataValues.id,
            name: rsp.dataValues.name,
            size: rsp.dataValues.size,
            category,
            path: res.path.slice(3), // 去掉app这层路径
          });
        }
      } catch (e) {
        ctx.logger.error('文件上传失败', e);
        return ctx.helper.body.ERROR({ ctx, msg: '文件上传失败' });
      }
    }
    return ctx.helper.body.SUCCESS({ ctx, msg: '文件上传成功', data: single ? rets[0] : rets });
  }

  // 获取文件信息
  async checkFileInfo(file) {
    return new Promise((resolve, reject) => {
      const fsHash = crypto.createHash('md5');
      fs.readFile(file.filepath, (err, data) => { // 读取文件
        if (err) {
          reject(err);
        } else {
          // 获取文件md5
          const md5 = fsHash.update(data).digest('hex');
          // 获取文件大小
          const size = data.length;
          resolve({ size, md5, data });
        }
      });
    });
  }

  // 保存文件到硬盘
  async putFile(fileBuffer, filename, category) {
    return new Promise((resolve, reject) => {
      const { dir, filePath } = this.createUploadPath(category);
      this.ctx.logger.info('====filePath====', filePath);
      const ext = this.getFileExt(filename);
      // 保存的文件名，使用的UUID
      const targetName = UUID() + '.' + ext;
      // 写文件
      fs.writeFile(dir + '/' + targetName, fileBuffer, err => {
        if (err) {
          reject(err);
        } else {
          resolve({
            path: filePath + '/' + targetName,
            ext,
          });
        }
      });
    });
  }

  // 数据库中用文件的md5查找是否存在
  async checkFileExists(md5) {
    const exists = await this.app.model.TFile.findOne({ where: { md5 } });
    // 如果文件存在就返回文件信息
    if (exists !== null) {
      return {
        id: exists.id,
        path: exists.path,
        name: exists.name,
        size: exists.size,
        category: exists.category,
      };
    }
    return null;
  }

  // 获取不带.的后缀名
  getFileExt(filename) {
    const ext = path.extname(filename);
    if (ext.startsWith('.')) {
      return ext.slice(1);
    }
    return ext;
  }

  async mkdirFile(dirname) {
    if (fs.existsSync(dirname)) {
      return true;
    }
    if (this.mkdirFile(path.dirname(dirname))) {
      fs.mkdirSync(dirname);
      return true;
    }
  }

  createUploadPath(category) {
    // 获取配置中文件存放位置
    const { app } = this;
    const disk = app.config.file.disk;
    const local = dayjs().format('YYYY-MM-DD');
    const dir = path.join(__dirname, '..', disk + `/${category}`);
    const filePath = `app${disk}/${category}`;
    this.mkdirFile(dir);
    return { dir, local, filePath };
  }


}

module.exports = FileService;
